/**
 * \file sdcard.c
 * SD card handler
 * 
 * Code for SD card interface and communication handling
 * 
 * AT91SAM7S-128 USB Mass Storage Device with SD Card by Michael Wolf\n
 * Copyright (C) 2008 Michael Wolf\n\n
 * 
 * This program is free software: you can redistribute it and/or modify\n
 * it under the terms of the GNU General Public License as published by\n
 * the Free Software Foundation, either version 3 of the License, or\n
 * any later version.\n\n
 * 
 * This program is distributed in the hope that it will be useful,\n
 * but WITHOUT ANY WARRANTY; without even the implied warranty of\n
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n
 * GNU General Public License for more details.\n\n
 * 
 * You should have received a copy of the GNU General Public License\n
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n
 * 
 */
#include <string.h>
#include "hardware_conf.h"
#include "firmware_conf.h"
#include "trace.h"
#include "macros.h"
#include "spi.h"
#include "delay.h"
#include "time.h"
#include "sdcard.h"

uint8_t ver2_card = false;  //!< Flag to indicate version 2.0 SD card
uint8_t sdhc_card = false;  //!< Flag to indicate version SDHC card
uint32_t sd_numsectors; //!< Total number of sectors on card
uint8_t sd_sectorbuffer[512];   //!< buffer to hold one sector of card

/**
 * Return SD card present status.
 * 
 * Indicate the presence of a memory card in the SD slot.
 * \return  Card Status
 * 
 */
uint8_t sd_card_detect(void)
{
    pPIO->PIO_PER = PIN_CARD_DETECT;
    pPIO->PIO_ODR = PIN_CARD_DETECT;
    pPIO->PIO_PPUER = PIN_CARD_DETECT;
    
    if (ISSET(pPIO->PIO_PDSR,PIN_CARD_DETECT))
        return SD_OK;            
    else
        return SD_NOCARD;
}

/**
 * SD command
 * 
 * Send SD command to SD card
 * \param   cmd     Card command
 * \param   arg     Argument
 * 
 */
void sd_command(uint8_t cmd, uint32_t arg)
{
    spi_byte(0xff,0);        // dummy byte
    spi_byte(cmd | 0x40,0);  // send command
    spi_byte(arg>>24,0);     // send argument
    spi_byte(arg>>16,0);
    spi_byte(arg>>8,0);
    spi_byte(arg,0);
    
    switch(cmd)
    {
    	case SD_GO_IDLE_STATE:
    	    spi_byte(0x95,1); // CRC for CMD0
    	    break;
    	/*
    	 * CRC for CMD8 always enabled see:
    	 * Physical Layer Simplified Specification Version 2.00
    	 * chapter 7.2.2 Bus Transfer Protection
    	 */
    	case SD_SEND_IF_COND:
    		spi_byte(0x87,1); // CRC for CMD8, argument 0x000001AA, see sd_init
    		break;
    		
    	default:
    		spi_byte(0xFF,1); // send dummy CRC for all other commands
    }
}

/**
 * Send dummys
 * 
 * Send 10 dummy bytes to card
 * 
 */
void sd_send_dummys(void)
{
    for(uint8_t i=0; i < 9; i++)
        spi_byte(0xff,0);
    
    spi_byte(0xff,1);
}

/**
 * Get response
 * 
 * Get card response tokens
 * \return Response token
 * 
 */
uint8_t sd_get_response(void)
{
    uint32_t tmout = timeval + 1000;    // 1 second timeout
    uint8_t b = 0xff;

    while ((b == 0xff) && (timeval < tmout)) 
    {
        b = spi_byte(0xff,0);
    }
    return b;
}

/**
 * Get data token
 * 
 * Get card data response tokens
 * \return Data token
 * 
 */
uint8_t sd_get_datatoken(void)
{
    uint32_t tmout = timeval + 1000;    // 1 second timeout
    uint8_t b = 0xff;

    while ((b != SD_STARTBLOCK_READ) && (timeval < tmout)) 
    {
        b = spi_byte(0xff,0);
    }
    return b;
}

/**
 * Init SD card
 * 
 * Init SD card
 * \return  Error code
 * 
 */
int8_t sd_init(void)
{
    // Card not initalized
    
    // setup card detect on PA18
    pPIO->PIO_PER   = PIN_CARD_DETECT;          // Enable PIO pin
    pPIO->PIO_ODR   = PIN_CARD_DETECT;          // Enable input
    pPIO->PIO_PPUER = PIN_CARD_DETECT;         // Enable pullup
    
    uint8_t retries;
    uint8_t resp;

    TRACE_SD("Init SD card\n");
    
    for(retries = 0, resp = 0; (retries < 5) && (resp != SD_R1_IDLE_STATE) ; retries++)
    {
        // send CMD0 to reset card
    	sd_command(SD_GO_IDLE_STATE,0);
        resp = sd_get_response();
 
        TRACE_SD("go idle resp: %X\n", resp);
        delayms(100);
    }
    
    if(resp != SD_R1_IDLE_STATE) return SD_E_IDLE;

    // send CMD8 to check voltage range
    // this also determines if the card is a 2.0 (or later) card
    sd_command(SD_SEND_IF_COND,0x000001AA);
    resp = sd_get_response();

    TRACE_SD("CMD8resp: %02X\n",resp);    
    
    if ((resp & SD_R1_ILLEGAL_COM) != SD_R1_ILLEGAL_COM)
    {
    	TRACE_SD("2.0 card\n");
        uint32_t r7reply;
        ver2_card = true;  // mark this as a version2 card
        r7reply = sd_get_response();     
        r7reply <<= 8;
        r7reply |= sd_get_response();    
        r7reply <<= 8;
        r7reply |= sd_get_response();    
        r7reply <<= 8;
        r7reply |= sd_get_response();

        TRACE_SD("CMD8REPLY: %08x\n",r7reply);
        
        // verify that we're compatible
        if ( (r7reply & 0x00000fff) != 0x01AA )
        {
            TRACE_SD("Voltage range mismatch\n");
            return SD_E_VOLT;  // voltage range mismatch, unsuable card
        }
    }
    else
    {
         TRACE_SD("Not a 2.0 card\n");
    }   

    sd_send_dummys();

    /*
     * send ACMD41 until we get a 0 back, indicating card is done initializing
     * wait for max 5 seconds
     * 
     */
    for (retries=0,resp=0; !resp && retries<50; retries++)
    {
        uint8_t i;
        // send CMD55
        sd_command(SD_APP_CMD, 0);    // CMD55, prepare for APP cmd

        TRACE_SD("Sending CMD55\n");
        
        if ((sd_get_response() & 0xFE) != 0)
        {
             TRACE_SD("CMD55 failed\n");
        }
        // send ACMD41
        TRACE_SD("Sending ACMD41\n");
        
        if(ver2_card)
        	sd_command(SD_ACMD_SEND_OP_COND, 1UL << 30); // ACMD41, HCS bit 1
        else
        	sd_command(SD_ACMD_SEND_OP_COND, 0); // ACMD41, HCS bit 0
        
        i = sd_get_response();
        
        TRACE_SD("response = %02x\n",i);
        
        if (i != 0)
        {
            sd_send_dummys();
            delayms(100);
        }
        else    
            resp = 1;
        
        delayms(500);
    }

    if (!resp)
    {
        TRACE_SD("not valid\n");
        return SD_E_INIT;          // init failure
    }
    sd_send_dummys();     // clean up

    if (ver2_card)
    {
        uint32_t ocr;
        // check for High Cap etc
        
        // send CMD58
        TRACE_SD("sending CMD58\n");

        sd_command(SD_READ_OCR,0);    // CMD58, get OCR
        if (sd_get_response() != 0)
        {
            TRACE_SD("CMD58 failed\n");
        }
        else
        {
            // 0x80, 0xff, 0x80, 0x00 would be expected normally
            // 0xC0 if high cap
            
            ocr = sd_get_response();
            ocr <<= 8;
            ocr |= sd_get_response();
            ocr <<= 8;
            ocr |= sd_get_response();
            ocr <<= 8;
            ocr |= sd_get_response();
             
            TRACE_SD("OCR = %08x\n", ocr);
            
            if((ocr & 0xC0000000) == 0xC0000000)
            {
            	TRACE_SD("SDHC card.\n");
            	sdhc_card = true; // Set HC flag.
            }
        }
    }
    sd_send_dummys();     // clean up

    sd_info();

    TRACE_SD("Init SD card OK\n");
    return SD_OK;   
}

/**
 * Get Card Information.
 * 
 * Read and print some card information and return card size in bytes
 * 
 * \return     Cardsize in bytes
 * \todo This return value should be changed to allow cards > 4GB
 * 
*/
uint32_t sd_info(void)
{
    int i;
    uint32_t l;
    uint16_t w;
    uint8_t b;

    uint16_t csize;
    uint8_t csize_mult;
    uint32_t blockno;
    uint16_t mult;

#ifdef TR_SD // CID only needed for tracing
    sd_send_dummys();     // cleanup  
    
    sd_command(SD_SEND_CID,0);
    if (sd_get_datatoken() != SD_STARTBLOCK_READ) 
         TRACE_SD("Error during CID read\n");
    else 
    {
        TRACE_SD("CID read\n");

        TRACE_SD("Manufacturer ID: %02x\r\n",spi_byte(0xff,0));

        w = spi_byte(0xff,0);
        w <<= 8;
        w |= spi_byte(0xff,0);
       TRACE_SD("OEM/Application ID: %02x\n",w);

        TRACE_SD("Product Name: ");
        for (i=0;i<6;i++) 
            TRACE_SD("%c",spi_byte(0xff,0));

        TRACE_SD("\nProduct Revision: %02x\n",spi_byte(0xff,0));

        l = spi_byte(0xff,0);
        l <<= 8;
        l |= spi_byte(0xff,0);
        l <<= 8;
        l |= spi_byte(0xff,0);
        l <<= 8;
        l |= spi_byte(0xff,0);
        TRACE_SD("Serial Number: %08lx (%ld)\n",l,l);
        TRACE_SD("Manuf. Date Code: %02x\n",spi_byte(0xff,0));

    }

    spi_byte(0xff,0);    // skip checksum

#endif
    sd_send_dummys();
    
    sd_command(SD_SEND_CSD,0);
    if ((b = sd_get_datatoken()) != SD_STARTBLOCK_READ) 
    {
        TRACE_SD("Error during CSD read, token was %02x\n",b);
        sd_send_dummys();
        return 0;
    }
    else 
    {
        TRACE_SD("CSD read\n");
    }

    // we need C_SIZE (bits 62-73 (bytes ) and C_SIZE_MULT (bits 47-49)
    //  0,  8 , 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96,104,112,120
    // 16  15   14  13  12  11  10   9   8   7   6   5   4   3   2   1

    // read 1 byte [bits 127:120]
    l = spi_byte(0xff,0);
    
    if(l == 0) // CSD 1.0 structure
    {
    	TRACE_SD("CSD 1.0\n");

    	// skip next 5 bytes [bits 119:80]
        for (i=0;i<5;i++) 
            spi_byte(0xff,0);
    	
        // get dword from [bits 79:56] 
        l = spi_byte(0xff,0);
        l <<= 8;
        l |= spi_byte(0xff,0);
        l <<= 8;
        l |= spi_byte(0xff,0);

        // shift down to to access [bits 73:62]
        l >>= 6;
        csize = (uint16_t) (l & 0x0fff);
        TRACE_SD("C_SIZE = %04x\n",csize);

        // get word from [bits 55:40]
        w = spi_byte(0xff,0);
        w <<= 8;
        w |= spi_byte(0xff,0);

        // shift down to to access [bits 49:47]
        w >>= 7;
        csize_mult = (uint16_t) (w & 0x07);
        TRACE_SD("C_SIZE_MULT = %02x\n",csize_mult);

        mult = 1 << (csize_mult+2);
        blockno = (uint32_t) ((uint16_t)csize+1) * mult;
        TRACE_SD("mult = %0d\n",mult);
        TRACE_SD("blockno = %ld\n",blockno);
        TRACE_SD("card size = %lu / (%lu MByte)\n\n", blockno * 512L,blockno / 2048L);

        sd_send_dummys();
        sd_numsectors = blockno;

        return blockno * 512L;
    }
    else
    if( l == 0x40) // CSD 2.0 structure
    {
    	TRACE_SD("CSD 2.0\n");
    	
        // skip next 6 bytes [bits 119:72]
        for (i=0;i<6;i++) 
            spi_byte(0xff,0);
        
        // get dword from [bits 71:48] 
        l = spi_byte(0xff,0);
        l <<= 8;
        l |= spi_byte(0xff,0);
        l <<= 8;
        l |= spi_byte(0xff,0);

        l &= 0x0000ffff; // mask c_size field
        
        uint32_t byte_size = ((l+1) * 524288L);
        
        TRACE_SD("C_SIZE = %08x\n",l);
        TRACE_SD("card size = %lu / (%lu MByte)\n\n",byte_size , ((l+1)>>1));

        sd_send_dummys();
        sd_numsectors = (byte_size / 512)-1;

        return byte_size;
    }
    else
    {
    	TRACE_SD("Invalid CSD structure!\n");
    	sd_numsectors = 0;
    	return 0;    	
    }
}

/**
 * Read SD sector.
 * 
 * Read a single 512 byte sector from the SD card
 * \param   lba         Logical sectornumber to read from
 * \param   buffer      Pointer to buffer for received data
 * \param   fCallback   Callback function name
 * \param   pArgument   Callback argument
 * \return 0 on success, -1 on error
 * 
*/  
int8_t sd_readsector(uint32_t lba,
                     uint8_t *buffer,
                     Callback_f   fCallback,
                     void *pArgument)
{
    
    TRACE_SD("SDrd%ld   ", lba);

    if(sdhc_card)
    	 // on new High Capacity cards, the lba is sent
    	sd_command(SD_READ_SINGLE_BLOCK,lba);
    else
    	sd_command(SD_READ_SINGLE_BLOCK,lba<<9);
    	// send read command and logical sector address
    	// the address sent to the card is the BYTE address
    	// so the lba needs to be multiplied by 512
    
    if (sd_get_response() != 0) // if no valid token
    {
        sd_send_dummys(); // cleanup and  
        return SD_ERROR;   // return error code
    }

    if (sd_get_datatoken() != SD_STARTBLOCK_READ) // if no valid token
    {
        sd_send_dummys(); // cleanup and  
        return SD_ERROR;   // return error code
    }

    for (uint16_t i=0; i<512 ; i++)             // read sector data
        *buffer++ = spi_byte(0xff,0);
        
    spi_byte(0xff,0);    // ignore dummy checksum
    spi_byte(0xff,0);    // ignore dummy checksum

    sd_send_dummys();     // cleanup

    // Invoke callback
    if (fCallback != 0) {

        fCallback((unsigned int) pArgument, SD_OK, 0, 0);
    }

    return SD_OK;                       // return success       
}


/** 
 * Read part of a SD sector.
 * 
 * Read part of a single 512 byte sector from the SD card
 * \param  block   Logical sectornumber to read from
 * \param  loffset Offset to first byte we should read
 * \param  nbytes  Number of bytes to read
 * \param  buffer  Pointer to buffer for received data
 * \return 1 on success, 0 on error
 * 
*/ 
uint8_t sd_read_n(uint32_t block,uint16_t loffset, uint16_t nbytes, uint8_t * buffer)
{
    sd_readsector(block,sd_sectorbuffer, 0, 0);
    memcpy(buffer,&sd_sectorbuffer[loffset],nbytes);
    return 0;
}


/**
 * Write SD sector.
 * 
 * Write a single 512 byte sector to the SD card
 * 
 * \param   lba         Logical sectornumber to write to
 * \param   buffer      Pointer to buffer with data to send
 * \param   fCallback   Callback function name
 * \param   pArgument   Callback argument
 * \return 0 on success, -1 on error
*/  
int8_t sd_writesector(uint32_t lba,
                      uint8_t *buffer,
                      Callback_f   fCallback,
                      void *pArgument)
{
    uint16_t i;
    uint32_t tmout;

    TRACE_SD("SDwr%ld   ", lba);

    if(sdhc_card)
    	 // on new High Capacity cards, the lba is sent
    	sd_command(SD_WRITE_BLOCK,lba);
    else
    	sd_command(SD_WRITE_BLOCK,lba<<9);
    	// send read command and logical sector address
    	// the address sent to the card is the BYTE address
    	// so the lba needs to be multiplied by 512
    
    if (sd_get_response() != 0) // if no valid token
    {
        sd_send_dummys(); // cleanup and
        return SD_ERROR;   // return error code
    }

    spi_byte(0xfe,0);    // send data token

    for (i=0;i<512;i++)             // write sector data
    {
        spi_byte(*buffer++,0);
    }

    spi_byte(0xff,0);    // send dummy checksum
    spi_byte(0xff,0);    // send dummy checksum

    if ( (sd_get_response()&0x0F) != 0x05) // if no valid token
    {
        sd_send_dummys(); // cleanup and
        return SD_ERROR;   // return error code
    }

    //
    // wait while the card is busy
    // writing the data
    //
    tmout = timeval + 1000;

    // wait for the SO pin to go high
    while (1)
    {
        uint8_t b = spi_byte(0xff,0);

        if (b == 0xff) break;   // check SO high
        
        if (timeval > tmout)    // if timeout
        {
            sd_send_dummys();   // cleanup and
            return SD_ERROR;    // return failure
        }

    }
    sd_send_dummys(); // cleanup  
    
    // Invoke callback
    if (fCallback != 0) {

        fCallback((unsigned int) pArgument, SD_OK, 0, 0);
    }
   
    return SD_OK;   // return success
}
